package com.netty.server.base;

import com.netty.server.sender.Callback;
import com.netty.server.sender.CmdRequest;
import com.netty.server.sender.CmdResponse;
import com.netty.server.utils.CmdIdGenerator;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RequestRegister {
    /**
     * 请求注册表
     */
    private final Map<Long, Context> contextMap;
    /**
     * cmdId生成器
     */
    private final CmdIdGenerator cmdIdGenerator;
    /**
     * 业务回调线程池
     */
    private final EventExecutorGroup group;
    /**
     * 默认超时时间（单位：毫秒）
     */
    private final long timeoutMs;
    /**
     * 释放标志位
     */
    private volatile boolean released = false;

    /**
     * 构造函数
     *
     * @param group 业务回调线程池
     */
    public RequestRegister(EventExecutorGroup group) {
        this.contextMap = new ConcurrentHashMap<>();
        this.cmdIdGenerator = new CmdIdGenerator();
        this.group = group;
        this.timeoutMs = 60 * 1000;
    }

    /**
     * 注册请求
     *
     * @param request 请求对象
     */
    public void register(CmdRequest request) {
        // 如果对象已经释放，无视注册请求
        if (released) {
            return;
        }

        // 如果回调为null，则无视注册请求
        if (request.getCallback() == null) {
            return;
        }

        // 构建上下文
        Context ctx = new Context(request);

        // 注册上下文
        contextMap.put(ctx.getCmdId(), ctx);
    }

    /**
     * 处理响应
     *
     * @param response 响应对象
     */
    public void handle(CmdResponse response) {
        // 如果对象已经释放，无视处理请求
        if (released) {
            return;
        }

        // 提取cmdId并找到与之匹配的请求上下文
        long cmdId = response.getCmdId();
        Context ctx = contextMap.get(cmdId);
        // 如果没有找到请求，则无视处理请求
        if (ctx == null) {
            return;
        }

        // 交由业务线程池调用回调方法
        ctx.executor.execute(() -> {
            // 如果Context已经被移除，就不再执行了（可能在执行前就已经完成、释放或者超时处理掉了请求）
            if (!contextMap.containsKey(cmdId)) {
                return;
            }

            Callback callback = ctx.request.getCallback();
            try {
                boolean finish = callback.onNext(ctx.request, response);
                if (finish) {
                    // 结束接收响应，取消超时监测，从注册表中移除请求上下文
                    ctx.future.cancel(false);
                    contextMap.remove(cmdId);
                }
            } catch (Exception e) {
                log.info("接收回调失败!", e);
            }
        });
    }

    /**
     * 释放Register。释放时会调用所有未被处理的请求的超时回调
     */
    public void release() {
        // 无视重复释放
        if (released) {
            return;
        }

        // 置位释放标志位
        released = true;

        // 逐个取消请求等待
        Iterator<Context> iterator = contextMap.values().iterator();
        while (iterator.hasNext()) {
            Context ctx = iterator.next();
            iterator.remove();
            ctx.future.cancel(false);
            ctx.executor.execute(() -> {
                // 如果Context已经被移除，就不再执行了（可能在执行前就已经完成、释放或者超时处理掉了请求）
                if (!contextMap.containsKey(ctx.getCmdId())) {
                    return;
                }

                ctx.request.getCallback().onTimeout(ctx.request);
            });
        }
    }

    /**
     * 上下文对象
     */
    private class Context {
        /**
         * 请求对象
         */
        private final CmdRequest request;
        /**
         * 超时监测future
         */
        private final ScheduledFuture<?> future;
        /**
         * 与请求绑定的业务线程
         */
        private final EventExecutor executor;

        public Context(CmdRequest request) {
            // 获取并设置cmdId
            long cmdId = cmdIdGenerator.nextCmdId();
            request.setCmdId(cmdId);
            this.request = request;
            // 将一个请求绑定到业务线程池的一个线程上
            this.executor = group.next();
            // 如果请求设置了超时时长就使用请求的超时时长，否则使用默认的
            long timeout = request.getTimeout() != null ? request.getTimeout() : timeoutMs;
            this.future = this.executor.schedule(() -> {
                // 由于完成和释放都会取消超时任务，所以这里就不再检查了
                try {
                    contextMap.remove(cmdId);
                    request.getCallback().onTimeout(request);
                } catch (Exception e) {
                    log.info("超时回调失败!", e);
                }
            }, timeout, TimeUnit.MILLISECONDS);
        }

        public long getCmdId() {
            return request.getCmdId();
        }
    }

}
